package server

import (
	"encoding/json"
	"fmt"
	"game/types"
	"game/utils"
	"io"
	"log"
	"math/rand"
	"net"
	"sync"
	"time"

	"golang.org/x/exp/slices"
)

type Server struct {
	id         string
	listenAddr string
	gameServer *GameServer

	quitch chan struct{}
	msgch  chan Message
	ln     net.Listener
}

type GameServer struct {
	gamestate *types.GameState

	mu     sync.RWMutex
	msgch  chan Message
	pingch chan bool
	status string
}

type Message struct {
	conn    net.Conn
	payload []byte
}

// just for players to handle message
func InitServer(id string, listenAddr string) *Server {
	fmt.Println("init a normal server")

	server := &Server{
		id:         id,
		listenAddr: listenAddr,
		msgch:      make(chan Message, 10),
	}
	go server.handlePlayersConnection()
	go server.handleMsgChan()
	return server
}

// func (gameServer *GameServer) getGameState() types.GameState {
// 	gameServer.mu.RLock()
// 	defer gameServer.mu.RUnlock()
// 	return *gameServer.gamestate
// }

func (gameServer *GameServer) changeGameState(value int) {
	gameServer.mu.Lock()
	defer gameServer.mu.Unlock()
	// gameServer. -= value
}

func (gameServer *GameServer) ping() {
	fmt.Println("primary pinging")
	alivePlayers := []types.Player{}
	alivePlayerIds := []string{}

	gameServer.mu.Lock()
	for index, p := range gameServer.gamestate.Players {
		addr := p.PlayerAddr
		if err := gameServer.sendPingMessage(index, addr); err == nil {
			alivePlayers = append(alivePlayers, p)
			alivePlayerIds = append(alivePlayerIds, p.PlayerId)
		}
	}

	if len(alivePlayers) == len(gameServer.gamestate.Players) {
		fmt.Println("players all good, len: ", len(alivePlayers))
		gameServer.mu.Unlock()
		return
	}

	gameServer.gamestate.Players = alivePlayers
	for x, row := range gameServer.gamestate.Mazemap {
		for y, id := range row {
			if !slices.Contains(alivePlayerIds, id) {
				gameServer.gamestate.Mazemap[x][y] = ""
			}
		}
	}

	b, err := json.Marshal(gameServer.gamestate)
	if err != nil {
		panic(err)
	}

	gameServer.mu.Unlock()

	// backup if change
	if gameServer.gamestate.BackupServer.PlayerAddr != "" {
		go gameServer.sendToBackup(b)
	}
}

func (gameServer *GameServer) sendPingMessage(index int, addr string) error {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		fmt.Println("sending to server: error")
		// remove player
		return err
	}
	defer conn.Close()

	msg := types.ReqToServer{
		Type: "ping",
		Id:   gameServer.gamestate.PrimaryServer.PlayerId,
	}

	conn.Write(utils.Marshal(msg))
	return nil
}

func (s *Server) backupReceivePing() {
	for {
		timer := time.AfterFunc(time.Millisecond*1100, s.changeToPrimary)
		switch {
		case <-s.gameServer.pingch:
			<-timer.C
		default:
		}

	}
}

func (s *Server) changeToPrimary() {
	s.gameServer.mu.Lock()
	// change to primary
	s.gameServer.gamestate.PrimaryServer.PlayerAddr = s.listenAddr
	s.gameServer.gamestate.PrimaryServer.PlayerId = s.id
	s.gameServer.status = "primary_server"

	// remove primary

	// assign backup

	// tell tracker

	s.gameServer.mu.Unlock()
}

// only call when game start
func (s *Server) NewPrimaryServer(K int, N int) {
	fmt.Println("init primary server")

	s.gameServer = s.NewGameServer(K, N)
	// ping and updata game state every 0.5s
	for {
		// ping every 1s
		time.Sleep(time.Millisecond * 1000)
		s.gameServer.ping()
	}
}

func (s *Server) handlePlayersConnection() error {
	fmt.Println("listen to: " + s.listenAddr)
	ln, err := net.Listen("tcp", s.listenAddr)
	if err != nil {
		fmt.Println("server listen error ")
		return err
	}
	defer ln.Close()

	s.ln = ln

	go s.readMsgFromPlayer()

	<-s.quitch
	return nil
}

// accept connection from players
func (s *Server) readMsgFromPlayer() {
	for {
		conn, acceptErr := s.ln.Accept()
		if acceptErr != nil {
			// do not return, keep accepting new conn
			fmt.Println("accept error: ", acceptErr)
			continue
		}
		defer conn.Close()

		buf := make([]byte, 8192)
		n, readErr := conn.Read(buf)
		if readErr != nil {
			fmt.Println("read error: ", readErr)
			continue
		}

		s.msgch <- Message{
			conn:    conn,
			payload: buf[:n],
		}

	}
}

func (s *Server) NewGameServer(K int, N int) *GameServer {
	gameState := &types.GameState{
		Players:   []types.Player{},
		StartTime: time.Now().Format("15:04:05"),
		PrimaryServer: types.PlayerAddr{
			PlayerId:   s.id,
			PlayerAddr: s.listenAddr,
		},
	}

	mazemap := make([][]string, N)
	for i := range mazemap {
		mazemap[i] = make([]string, N)
	}

	for i := 0; i < K; i++ {
		x := rand.Intn(N)
		y := rand.Intn(N)

		if mazemap[x][y] != "" && (x == 0 && y == 0) {
			continue
		}
		mazemap[x][y] = "*"
	}
	gameState.Mazemap = mazemap

	gameServer := &GameServer{
		gamestate: gameState,
		msgch:     make(chan Message, 50),
		status:    "primary_server",
	}
	return gameServer
}

func (s *Server) handleMsgChan() {
	for msg := range s.msgch {
		// for normal player
		if s.gameServer == nil {
			fmt.Println("msg in 1")

			s.handleMessage(msg)
			continue
		} else {
			// for primary server and backup server
			fmt.Println("msg in 2")
			s.gameServer.handleMessage(msg)
			continue
		}
	}
}

// normal player only response when regenerate backup
func (s *Server) handleMessage(message Message) {
	request := types.ReqToServer{}
	if err := json.Unmarshal(message.payload, &request); err != nil {
		fmt.Println("unmarshal error")
		return
	}

	if request.Type == "generate_backup_server" {
		gameState := types.GameState{}
		if err := json.Unmarshal(request.Data, &gameState); err != nil {
			panic(err)
		}
		s.gameServer = &GameServer{
			gamestate: &gameState,
			msgch:     make(chan Message, 50),
			status:    "backup_server",
		}
		// ping primary
		fmt.Println("generate_backup_server")
		message.conn.Write([]byte("ok"))
	}
}

func (gameServer *GameServer) handleMessage(message Message) {
	request := types.ReqToServer{}
	if err := json.Unmarshal(message.payload, &request); err != nil {
		fmt.Println("unmarshal error: ", err)
		return
		// panic(err)
	}

	// backup server only backup
	if gameServer.status == "backup_server" && request.Type == "backup" {
		gamestate := types.GameState{}
		if err := json.Unmarshal(request.Data, &gamestate); err != nil {
			panic(err)
		}
		gameServer.gamestate = &gamestate
		fmt.Println("backup success: ", gameServer.gamestate.Test)
		return
	}

	if gameServer.status == "backup_server" && request.Type == "ping" {
		fmt.Println("primary is alive")
		gameServer.pingch <- true
		return
	}

	// primary server
	switch request.Type {
	case "join":
		playerInfo := types.PlayerAddr{}
		if err := json.Unmarshal(request.Data, &playerInfo); err != nil {
			panic(err)
		}
		gameServer.addPlayer(playerInfo)

		// if it is second player, call it to be backup
		if len(gameServer.gamestate.Players) == 2 {
			if err := gameServer.assignBackupServer(playerInfo); err == nil {
				gameServer.gamestate.BackupServer = playerInfo
			}
		}
	case "ping":
		// fmt.Println("ping")
	case "refresh":
		fmt.Println("refreshing")
	case "up":
		fmt.Println("up")
	case "left":
		fmt.Println("left")
	case "right":
		fmt.Println("right")
	case "down":
		fmt.Println("douwn")
	default:
		// fmt.Println("default: " + string(message.payload))
		panic("invalid message received")
	}

	gameServer.gamestate.Test += 1
	b, err := json.Marshal(gameServer.gamestate)
	if err != nil {
		panic(err)
	}
	// send to player and backup at the same time
	if gameServer.gamestate.BackupServer.PlayerAddr != "" {
		go gameServer.sendToBackup(b)
	}
	message.conn.Write(b)

}

func (gameServer *GameServer) addPlayer(player types.PlayerAddr) {
	gameState := gameServer.gamestate
	N := len(gameState.Mazemap)

	for {
		x := rand.Intn(N)
		y := rand.Intn(N)

		if gameState.Mazemap[x][y] == "" {
			gameState.Mazemap[x][y] = player.PlayerId

			gameState.Players = append(gameState.Players, types.Player{
				PlayerId:   player.PlayerId,
				PlayerAddr: player.PlayerAddr,
				Score:      0,
				PositionX:  x,
				PositionY:  y,
			})
			break
		}
	}
}

func (gameServer *GameServer) sendToBackup(marshaledGameState []byte) error {
	conn, err := net.Dial("tcp", gameServer.gamestate.BackupServer.PlayerAddr)
	if err != nil {
		fmt.Println("sending to server: error")
		return err
	}
	defer conn.Close()

	msg := types.ReqToServer{
		Type: "backup",
		Id:   gameServer.gamestate.PrimaryServer.PlayerId,
		Data: marshaledGameState,
	}

	conn.Write(utils.Marshal(msg))
	return nil
}

func (gameServer *GameServer) assignBackupServer(playerInfo types.PlayerAddr) error {
	fmt.Println("assigning a backup server")

	conn, err := net.Dial("tcp", playerInfo.PlayerAddr)
	if err != nil {
		fmt.Println("assigning backup server: error")
		return err
	}
	defer conn.Close()

	msg := types.ReqToServer{
		Type: "generate_backup_server",
		Id:   gameServer.gamestate.PrimaryServer.PlayerId,
		Data: utils.Marshal(gameServer.gamestate),
	}

	conn.Write(utils.Marshal(msg))

	buffer := make([]byte, 8192)
	n, readErr := conn.Read(buffer)
	if readErr != nil && readErr != io.EOF {
		log.Fatal(readErr)
	}

	if string(buffer[:n]) != "ok" {
		return fmt.Errorf("assign backup server error")
	}
	return nil
}
